home *** CD-ROM | disk | FTP | other *** search
/ Freelog 100 / FreelogNo100-NovembreDecembre2010.iso / Musique / solfege / solfege-win32-3.17.0.exe / {app} / bin / Lib / trace.py < prev    next >
Text File  |  2007-12-10  |  30KB  |  795 lines

  1. #!/usr/bin/env python
  2.  
  3. # portions copyright 2001, Autonomous Zones Industries, Inc., all rights...
  4. # err...  reserved and offered to the public under the terms of the
  5. # Python 2.2 license.
  6. # Author: Zooko O'Whielacronx
  7. # http://zooko.com/
  8. # mailto:zooko@zooko.com
  9. #
  10. # Copyright 2000, Mojam Media, Inc., all rights reserved.
  11. # Author: Skip Montanaro
  12. #
  13. # Copyright 1999, Bioreason, Inc., all rights reserved.
  14. # Author: Andrew Dalke
  15. #
  16. # Copyright 1995-1997, Automatrix, Inc., all rights reserved.
  17. # Author: Skip Montanaro
  18. #
  19. # Copyright 1991-1995, Stichting Mathematisch Centrum, all rights reserved.
  20. #
  21. #
  22. # Permission to use, copy, modify, and distribute this Python software and
  23. # its associated documentation for any purpose without fee is hereby
  24. # granted, provided that the above copyright notice appears in all copies,
  25. # and that both that copyright notice and this permission notice appear in
  26. # supporting documentation, and that the name of neither Automatrix,
  27. # Bioreason or Mojam Media be used in advertising or publicity pertaining to
  28. # distribution of the software without specific, written prior permission.
  29. #
  30. """program/module to trace Python program or function execution
  31.  
  32. Sample use, command line:
  33.   trace.py -c -f counts --ignore-dir '$prefix' spam.py eggs
  34.   trace.py -t --ignore-dir '$prefix' spam.py eggs
  35.   trace.py --trackcalls spam.py eggs
  36.  
  37. Sample use, programmatically
  38.   import sys
  39.  
  40.   # create a Trace object, telling it what to ignore, and whether to
  41.   # do tracing or line-counting or both.
  42.   tracer = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix,], trace=0,
  43.                     count=1)
  44.   # run the new command using the given tracer
  45.   tracer.run('main()')
  46.   # make a report, placing output in /tmp
  47.   r = tracer.results()
  48.   r.write_results(show_missing=True, coverdir="/tmp")
  49. """
  50.  
  51. import linecache
  52. import os
  53. import re
  54. import sys
  55. import threading
  56. import token
  57. import tokenize
  58. import types
  59. import gc
  60.  
  61. try:
  62.     import cPickle
  63.     pickle = cPickle
  64. except ImportError:
  65.     import pickle
  66.  
  67. def usage(outfile):
  68.     outfile.write("""Usage: %s [OPTIONS] <file> [ARGS]
  69.  
  70. Meta-options:
  71. --help                Display this help then exit.
  72. --version             Output version information then exit.
  73.  
  74. Otherwise, exactly one of the following three options must be given:
  75. -t, --trace           Print each line to sys.stdout before it is executed.
  76. -c, --count           Count the number of times each line is executed
  77.                       and write the counts to <module>.cover for each
  78.                       module executed, in the module's directory.
  79.                       See also `--coverdir', `--file', `--no-report' below.
  80. -l, --listfuncs       Keep track of which functions are executed at least
  81.                       once and write the results to sys.stdout after the
  82.                       program exits.
  83. -T, --trackcalls      Keep track of caller/called pairs and write the
  84.                       results to sys.stdout after the program exits.
  85. -r, --report          Generate a report from a counts file; do not execute
  86.                       any code.  `--file' must specify the results file to
  87.                       read, which must have been created in a previous run
  88.                       with `--count --file=FILE'.
  89.  
  90. Modifiers:
  91. -f, --file=<file>     File to accumulate counts over several runs.
  92. -R, --no-report       Do not generate the coverage report files.
  93.                       Useful if you want to accumulate over several runs.
  94. -C, --coverdir=<dir>  Directory where the report files.  The coverage
  95.                       report for <package>.<module> is written to file
  96.                       <dir>/<package>/<module>.cover.
  97. -m, --missing         Annotate executable lines that were not executed
  98.                       with '>>>>>> '.
  99. -s, --summary         Write a brief summary on stdout for each file.
  100.                       (Can only be used with --count or --report.)
  101.  
  102. Filters, may be repeated multiple times:
  103. --ignore-module=<mod> Ignore the given module and its submodules
  104.                       (if it is a package).
  105. --ignore-dir=<dir>    Ignore files in the given directory (multiple
  106.                       directories can be joined by os.pathsep).
  107. """ % sys.argv[0])
  108.  
  109. PRAGMA_NOCOVER = "#pragma NO COVER"
  110.  
  111. # Simple rx to find lines with no code.
  112. rx_blank = re.compile(r'^\s*(#.*)?$')
  113.  
  114. class Ignore:
  115.     def __init__(self, modules = None, dirs = None):
  116.         self._mods = modules or []
  117.         self._dirs = dirs or []
  118.  
  119.         self._dirs = map(os.path.normpath, self._dirs)
  120.         self._ignore = { '<string>': 1 }
  121.  
  122.     def names(self, filename, modulename):
  123.         if self._ignore.has_key(modulename):
  124.             return self._ignore[modulename]
  125.  
  126.         # haven't seen this one before, so see if the module name is
  127.         # on the ignore list.  Need to take some care since ignoring
  128.         # "cmp" musn't mean ignoring "cmpcache" but ignoring
  129.         # "Spam" must also mean ignoring "Spam.Eggs".
  130.         for mod in self._mods:
  131.             if mod == modulename:  # Identical names, so ignore
  132.                 self._ignore[modulename] = 1
  133.                 return 1
  134.             # check if the module is a proper submodule of something on
  135.             # the ignore list
  136.             n = len(mod)
  137.             # (will not overflow since if the first n characters are the
  138.             # same and the name has not already occurred, then the size
  139.             # of "name" is greater than that of "mod")
  140.             if mod == modulename[:n] and modulename[n] == '.':
  141.                 self._ignore[modulename] = 1
  142.                 return 1
  143.  
  144.         # Now check that __file__ isn't in one of the directories
  145.         if filename is None:
  146.             # must be a built-in, so we must ignore
  147.             self._ignore[modulename] = 1
  148.             return 1
  149.  
  150.         # Ignore a file when it contains one of the ignorable paths
  151.         for d in self._dirs:
  152.             # The '+ os.sep' is to ensure that d is a parent directory,
  153.             # as compared to cases like:
  154.             #  d = "/usr/local"
  155.             #  filename = "/usr/local.py"
  156.             # or
  157.             #  d = "/usr/local.py"
  158.             #  filename = "/usr/local.py"
  159.             if filename.startswith(d + os.sep):
  160.                 self._ignore[modulename] = 1
  161.                 return 1
  162.  
  163.         # Tried the different ways, so we don't ignore this module
  164.         self._ignore[modulename] = 0
  165.         return 0
  166.  
  167. def modname(path):
  168.     """Return a plausible module name for the patch."""
  169.  
  170.     base = os.path.basename(path)
  171.     filename, ext = os.path.splitext(base)
  172.     return filename
  173.  
  174. def fullmodname(path):
  175.     """Return a plausible module name for the path."""
  176.  
  177.     # If the file 'path' is part of a package, then the filename isn't
  178.     # enough to uniquely identify it.  Try to do the right thing by
  179.     # looking in sys.path for the longest matching prefix.  We'll
  180.     # assume that the rest is the package name.
  181.  
  182.     comparepath = os.path.normcase(path)
  183.     longest = ""
  184.     for dir in sys.path:
  185.         dir = os.path.normcase(dir)
  186.         if comparepath.startswith(dir) and comparepath[len(dir)] == os.sep:
  187.             if len(dir) > len(longest):
  188.                 longest = dir
  189.  
  190.     if longest:
  191.         base = path[len(longest) + 1:]
  192.     else:
  193.         base = path
  194.     base = base.replace(os.sep, ".")
  195.     if os.altsep:
  196.         base = base.replace(os.altsep, ".")
  197.     filename, ext = os.path.splitext(base)
  198.     return filename
  199.  
  200. class CoverageResults:
  201.     def __init__(self, counts=None, calledfuncs=None, infile=None,
  202.                  callers=None, outfile=None):
  203.         self.counts = counts
  204.         if self.counts is None:
  205.             self.counts = {}
  206.         self.counter = self.counts.copy() # map (filename, lineno) to count
  207.         self.calledfuncs = calledfuncs
  208.         if self.calledfuncs is None:
  209.             self.calledfuncs = {}
  210.         self.calledfuncs = self.calledfuncs.copy()
  211.         self.callers = callers
  212.         if self.callers is None:
  213.             self.callers = {}
  214.         self.callers = self.callers.copy()
  215.         self.infile = infile
  216.         self.outfile = outfile
  217.         if self.infile:
  218.             # Try to merge existing counts file.
  219.             try:
  220.                 counts, calledfuncs, callers = \
  221.                         pickle.load(open(self.infile, 'rb'))
  222.                 self.update(self.__class__(counts, calledfuncs, callers))
  223.             except (IOError, EOFError, ValueError), err:
  224.                 print >> sys.stderr, ("Skipping counts file %r: %s"
  225.                                       % (self.infile, err))
  226.  
  227.     def update(self, other):
  228.         """Merge in the data from another CoverageResults"""
  229.         counts = self.counts
  230.         calledfuncs = self.calledfuncs
  231.         callers = self.callers
  232.         other_counts = other.counts
  233.         other_calledfuncs = other.calledfuncs
  234.         other_callers = other.callers
  235.  
  236.         for key in other_counts.keys():
  237.             counts[key] = counts.get(key, 0) + other_counts[key]
  238.  
  239.         for key in other_calledfuncs.keys():
  240.             calledfuncs[key] = 1
  241.  
  242.         for key in other_callers.keys():
  243.             callers[key] = 1
  244.  
  245.     def write_results(self, show_missing=True, summary=False, coverdir=None):
  246.         """
  247.         @param coverdir
  248.         """
  249.         if self.calledfuncs:
  250.             print
  251.             print "functions called:"
  252.             calls = self.calledfuncs.keys()
  253.             calls.sort()
  254.             for filename, modulename, funcname in calls:
  255.                 print ("filename: %s, modulename: %s, funcname: %s"
  256.                        % (filename, modulename, funcname))
  257.  
  258.         if self.callers:
  259.             print
  260.             print "calling relationships:"
  261.             calls = self.callers.keys()
  262.             calls.sort()
  263.             lastfile = lastcfile = ""
  264.             for ((pfile, pmod, pfunc), (cfile, cmod, cfunc)) in calls:
  265.                 if pfile != lastfile:
  266.                     print
  267.                     print "***", pfile, "***"
  268.                     lastfile = pfile
  269.                     lastcfile = ""
  270.                 if cfile != pfile and lastcfile != cfile:
  271.                     print "  -->", cfile
  272.                     lastcfile = cfile
  273.                 print "    %s.%s -> %s.%s" % (pmod, pfunc, cmod, cfunc)
  274.  
  275.         # turn the counts data ("(filename, lineno) = count") into something
  276.         # accessible on a per-file basis
  277.         per_file = {}
  278.         for filename, lineno in self.counts.keys():
  279.             lines_hit = per_file[filename] = per_file.get(filename, {})
  280.             lines_hit[lineno] = self.counts[(filename, lineno)]
  281.  
  282.         # accumulate summary info, if needed
  283.         sums = {}
  284.  
  285.         for filename, count in per_file.iteritems():
  286.             # skip some "files" we don't care about...
  287.             if filename == "<string>":
  288.                 continue
  289.             if filename.startswith("<doctest "):
  290.                 continue
  291.  
  292.             if filename.endswith((".pyc", ".pyo")):
  293.                 filename = filename[:-1]
  294.  
  295.             if coverdir is None:
  296.                 dir = os.path.dirname(os.path.abspath(filename))
  297.                 modulename = modname(filename)
  298.             else:
  299.                 dir = coverdir
  300.                 if not os.path.exists(dir):
  301.                     os.makedirs(dir)
  302.                 modulename = fullmodname(filename)
  303.  
  304.             # If desired, get a list of the line numbers which represent
  305.             # executable content (returned as a dict for better lookup speed)
  306.             if show_missing:
  307.                 lnotab = find_executable_linenos(filename)
  308.             else:
  309.                 lnotab = {}
  310.  
  311.             source = linecache.getlines(filename)
  312.             coverpath = os.path.join(dir, modulename + ".cover")
  313.             n_hits, n_lines = self.write_results_file(coverpath, source,
  314.                                                       lnotab, count)
  315.  
  316.             if summary and n_lines:
  317.                 percent = int(100 * n_hits / n_lines)
  318.                 sums[modulename] = n_lines, percent, modulename, filename
  319.  
  320.         if summary and sums:
  321.             mods = sums.keys()
  322.             mods.sort()
  323.             print "lines   cov%   module   (path)"
  324.             for m in mods:
  325.                 n_lines, percent, modulename, filename = sums[m]
  326.                 print "%5d   %3d%%   %s   (%s)" % sums[m]
  327.  
  328.         if self.outfile:
  329.             # try and store counts and module info into self.outfile
  330.             try:
  331.                 pickle.dump((self.counts, self.calledfuncs, self.callers),
  332.                             open(self.outfile, 'wb'), 1)
  333.             except IOError, err:
  334.                 print >> sys.stderr, "Can't save counts files because %s" % err
  335.  
  336.     def write_results_file(self, path, lines, lnotab, lines_hit):
  337.         """Return a coverage results file in path."""
  338.  
  339.         try:
  340.             outfile = open(path, "w")
  341.         except IOError, err:
  342.             print >> sys.stderr, ("trace: Could not open %r for writing: %s"
  343.                                   "- skipping" % (path, err))
  344.             return 0, 0
  345.  
  346.         n_lines = 0
  347.         n_hits = 0
  348.         for i, line in enumerate(lines):
  349.             lineno = i + 1
  350.             # do the blank/comment match to try to mark more lines
  351.             # (help the reader find stuff that hasn't been covered)
  352.             if lineno in lines_hit:
  353.                 outfile.write("%5d: " % lines_hit[lineno])
  354.                 n_hits += 1
  355.                 n_lines += 1
  356.             elif rx_blank.match(line):
  357.                 outfile.write("       ")
  358.             else:
  359.                 # lines preceded by no marks weren't hit
  360.                 # Highlight them if so indicated, unless the line contains
  361.                 # #pragma: NO COVER
  362.                 if lineno in lnotab and not PRAGMA_NOCOVER in lines[i]:
  363.                     outfile.write(">>>>>> ")
  364.                     n_lines += 1
  365.                 else:
  366.                     outfile.write("       ")
  367.             outfile.write(lines[i].expandtabs(8))
  368.         outfile.close()
  369.  
  370.         return n_hits, n_lines
  371.  
  372. def find_lines_from_code(code, strs):
  373.     """Return dict where keys are lines in the line number table."""
  374.     linenos = {}
  375.  
  376.     line_increments = [ord(c) for c in code.co_lnotab[1::2]]
  377.     table_length = len(line_increments)
  378.     docstring = False
  379.  
  380.     lineno = code.co_firstlineno
  381.     for li in line_increments:
  382.         lineno += li
  383.         if lineno not in strs:
  384.             linenos[lineno] = 1
  385.  
  386.     return linenos
  387.  
  388. def find_lines(code, strs):
  389.     """Return lineno dict for all code objects reachable from code."""
  390.     # get all of the lineno information from the code of this scope level
  391.     linenos = find_lines_from_code(code, strs)
  392.  
  393.     # and check the constants for references to other code objects
  394.     for c in code.co_consts:
  395.         if isinstance(c, types.CodeType):
  396.             # find another code object, so recurse into it
  397.             linenos.update(find_lines(c, strs))
  398.     return linenos
  399.  
  400. def find_strings(filename):
  401.     """Return a dict of possible docstring positions.
  402.  
  403.     The dict maps line numbers to strings.  There is an entry for
  404.     line that contains only a string or a part of a triple-quoted
  405.     string.
  406.     """
  407.     d = {}
  408.     # If the first token is a string, then it's the module docstring.
  409.     # Add this special case so that the test in the loop passes.
  410.     prev_ttype = token.INDENT
  411.     f = open(filename)
  412.     for ttype, tstr, start, end, line in tokenize.generate_tokens(f.readline):
  413.         if ttype == token.STRING:
  414.             if prev_ttype == token.INDENT:
  415.                 sline, scol = start
  416.                 eline, ecol = end
  417.                 for i in range(sline, eline + 1):
  418.                     d[i] = 1
  419.         prev_ttype = ttype
  420.     f.close()
  421.     return d
  422.  
  423. def find_executable_linenos(filename):
  424.     """Return dict where keys are line numbers in the line number table."""
  425.     try:
  426.         prog = open(filename, "rU").read()
  427.     except IOError, err:
  428.         print >> sys.stderr, ("Not printing coverage data for %r: %s"
  429.                               % (filename, err))
  430.         return {}
  431.     code = compile(prog, filename, "exec")
  432.     strs = find_strings(filename)
  433.     return find_lines(code, strs)
  434.  
  435. class Trace:
  436.     def __init__(self, count=1, trace=1, countfuncs=0, countcallers=0,
  437.                  ignoremods=(), ignoredirs=(), infile=None, outfile=None):
  438.         """
  439.         @param count true iff it should count number of times each
  440.                      line is executed
  441.         @param trace true iff it should print out each line that is
  442.                      being counted
  443.         @param countfuncs true iff it should just output a list of
  444.                      (filename, modulename, funcname,) for functions
  445.                      that were called at least once;  This overrides
  446.                      `count' and `trace'
  447.         @param ignoremods a list of the names of modules to ignore
  448.         @param ignoredirs a list of the names of directories to ignore
  449.                      all of the (recursive) contents of
  450.         @param infile file from which to read stored counts to be
  451.                      added into the results
  452.         @param outfile file in which to write the results
  453.         """
  454.         self.infile = infile
  455.         self.outfile = outfile
  456.         self.ignore = Ignore(ignoremods, ignoredirs)
  457.         self.counts = {}   # keys are (filename, linenumber)
  458.         self.blabbed = {} # for debugging
  459.         self.pathtobasename = {} # for memoizing os.path.basename
  460.         self.donothing = 0
  461.         self.trace = trace
  462.         self._calledfuncs = {}
  463.         self._callers = {}
  464.         self._caller_cache = {}
  465.         if countcallers:
  466.             self.globaltrace = self.globaltrace_trackcallers
  467.         elif countfuncs:
  468.             self.globaltrace = self.globaltrace_countfuncs
  469.         elif trace and count:
  470.             self.globaltrace = self.globaltrace_lt
  471.             self.localtrace = self.localtrace_trace_and_count
  472.         elif trace:
  473.             self.globaltrace = self.globaltrace_lt
  474.             self.localtrace = self.localtrace_trace
  475.         elif count:
  476.             self.globaltrace = self.globaltrace_lt
  477.             self.localtrace = self.localtrace_count
  478.         else:
  479.             # Ahem -- do nothing?  Okay.
  480.             self.donothing = 1
  481.  
  482.     def run(self, cmd):
  483.         import __main__
  484.         dict = __main__.__dict__
  485.         if not self.donothing:
  486.             sys.settrace(self.globaltrace)
  487.             threading.settrace(self.globaltrace)
  488.         try:
  489.             exec cmd in dict, dict
  490.         finally:
  491.             if not self.donothing:
  492.                 sys.settrace(None)
  493.                 threading.settrace(None)
  494.  
  495.     def runctx(self, cmd, globals=None, locals=None):
  496.         if globals is None: globals = {}
  497.         if locals is None: locals = {}
  498.         if not self.donothing:
  499.             sys.settrace(self.globaltrace)
  500.             threading.settrace(self.globaltrace)
  501.         try:
  502.             exec cmd in globals, locals
  503.         finally:
  504.             if not self.donothing:
  505.                 sys.settrace(None)
  506.                 threading.settrace(None)
  507.  
  508.     def runfunc(self, func, *args, **kw):
  509.         result = None
  510.         if not self.donothing:
  511.             sys.settrace(self.globaltrace)
  512.         try:
  513.             result = func(*args, **kw)
  514.         finally:
  515.             if not self.donothing:
  516.                 sys.settrace(None)
  517.         return result
  518.  
  519.     def file_module_function_of(self, frame):
  520.         code = frame.f_code
  521.         filename = code.co_filename
  522.         if filename:
  523.             modulename = modname(filename)
  524.         else:
  525.             modulename = None
  526.  
  527.         funcname = code.co_name
  528.         clsname = None
  529.         if code in self._caller_cache:
  530.             if self._caller_cache[code] is not None:
  531.                 clsname = self._caller_cache[code]
  532.         else:
  533.             self._caller_cache[code] = None
  534.             ## use of gc.get_referrers() was suggested by Michael Hudson
  535.             # all functions which refer to this code object
  536.             funcs = [f for f in gc.get_referrers(code)
  537.                          if hasattr(f, "func_doc")]
  538.             # require len(func) == 1 to avoid ambiguity caused by calls to
  539.             # new.function(): "In the face of ambiguity, refuse the
  540.             # temptation to guess."
  541.             if len(funcs) == 1:
  542.                 dicts = [d for d in gc.get_referrers(funcs[0])
  543.                              if isinstance(d, dict)]
  544.                 if len(dicts) == 1:
  545.                     classes = [c for c in gc.get_referrers(dicts[0])
  546.                                    if hasattr(c, "__bases__")]
  547.                     if len(classes) == 1:
  548.                         # ditto for new.classobj()
  549.                         clsname = str(classes[0])
  550.                         # cache the result - assumption is that new.* is
  551.                         # not called later to disturb this relationship
  552.                         # _caller_cache could be flushed if functions in
  553.                         # the new module get called.
  554.                         self._caller_cache[code] = clsname
  555.         if clsname is not None:
  556.             # final hack - module name shows up in str(cls), but we've already
  557.             # computed module name, so remove it
  558.             clsname = clsname.split(".")[1:]
  559.             clsname = ".".join(clsname)
  560.             funcname = "%s.%s" % (clsname, funcname)
  561.  
  562.         return filename, modulename, funcname
  563.  
  564.     def globaltrace_trackcallers(self, frame, why, arg):
  565.         """Handler for call events.
  566.  
  567.         Adds information about who called who to the self._callers dict.
  568.         """
  569.         if why == 'call':
  570.             # XXX Should do a better job of identifying methods
  571.             this_func = self.file_module_function_of(frame)
  572.             parent_func = self.file_module_function_of(frame.f_back)
  573.             self._callers[(parent_func, this_func)] = 1
  574.  
  575.     def globaltrace_countfuncs(self, frame, why, arg):
  576.         """Handler for call events.
  577.  
  578.         Adds (filename, modulename, funcname) to the self._calledfuncs dict.
  579.         """
  580.         if why == 'call':
  581.             this_func = self.file_module_function_of(frame)
  582.             self._calledfuncs[this_func] = 1
  583.  
  584.     def globaltrace_lt(self, frame, why, arg):
  585.         """Handler for call events.
  586.  
  587.         If the code block being entered is to be ignored, returns `None',
  588.         else returns self.localtrace.
  589.         """
  590.         if why == 'call':
  591.             code = frame.f_code
  592.             filename = frame.f_globals.get('__file__', None)
  593.             if filename:
  594.                 # XXX modname() doesn't work right for packages, so
  595.                 # the ignore support won't work right for packages
  596.                 modulename = modname(filename)
  597.                 if modulename is not None:
  598.                     ignore_it = self.ignore.names(filename, modulename)
  599.                     if not ignore_it:
  600.                         if self.trace:
  601.                             print (" --- modulename: %s, funcname: %s"
  602.                                    % (modulename, code.co_name))
  603.                         return self.localtrace
  604.             else:
  605.                 return None
  606.  
  607.     def localtrace_trace_and_count(self, frame, why, arg):
  608.         if why == "line":
  609.             # record the file name and line number of every trace
  610.             filename = frame.f_code.co_filename
  611.             lineno = frame.f_lineno
  612.             key = filename, lineno
  613.             self.counts[key] = self.counts.get(key, 0) + 1
  614.  
  615.             bname = os.path.basename(filename)
  616.             print "%s(%d): %s" % (bname, lineno,
  617.                                   linecache.getline(filename, lineno)),
  618.         return self.localtrace
  619.  
  620.     def localtrace_trace(self, frame, why, arg):
  621.         if why == "line":
  622.             # record the file name and line number of every trace
  623.             filename = frame.f_code.co_filename
  624.             lineno = frame.f_lineno
  625.  
  626.             bname = os.path.basename(filename)
  627.             print "%s(%d): %s" % (bname, lineno,
  628.                                   linecache.getline(filename, lineno)),
  629.         return self.localtrace
  630.  
  631.     def localtrace_count(self, frame, why, arg):
  632.         if why == "line":
  633.             filename = frame.f_code.co_filename
  634.             lineno = frame.f_lineno
  635.             key = filename, lineno
  636.             self.counts[key] = self.counts.get(key, 0) + 1
  637.         return self.localtrace
  638.  
  639.     def results(self):
  640.         return CoverageResults(self.counts, infile=self.infile,
  641.                                outfile=self.outfile,
  642.                                calledfuncs=self._calledfuncs,
  643.                                callers=self._callers)
  644.  
  645. def _err_exit(msg):
  646.     sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
  647.     sys.exit(1)
  648.  
  649. def main(argv=None):
  650.     import getopt
  651.  
  652.     if argv is None:
  653.         argv = sys.argv
  654.     try:
  655.         opts, prog_argv = getopt.getopt(argv[1:], "tcrRf:d:msC:lT",
  656.                                         ["help", "version", "trace", "count",
  657.                                          "report", "no-report", "summary",
  658.                                          "file=", "missing",
  659.                                          "ignore-module=", "ignore-dir=",
  660.                                          "coverdir=", "listfuncs",
  661.                                          "trackcalls"])
  662.  
  663.     except getopt.error, msg:
  664.         sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
  665.         sys.stderr.write("Try `%s --help' for more information\n"
  666.                          % sys.argv[0])
  667.         sys.exit(1)
  668.  
  669.     trace = 0
  670.     count = 0
  671.     report = 0
  672.     no_report = 0
  673.     counts_file = None
  674.     missing = 0
  675.     ignore_modules = []
  676.     ignore_dirs = []
  677.     coverdir = None
  678.     summary = 0
  679.     listfuncs = False
  680.     countcallers = False
  681.  
  682.     for opt, val in opts:
  683.         if opt == "--help":
  684.             usage(sys.stdout)
  685.             sys.exit(0)
  686.  
  687.         if opt == "--version":
  688.             sys.stdout.write("trace 2.0\n")
  689.             sys.exit(0)
  690.  
  691.         if opt == "-T" or opt == "--trackcalls":
  692.             countcallers = True
  693.             continue
  694.  
  695.         if opt == "-l" or opt == "--listfuncs":
  696.             listfuncs = True
  697.             continue
  698.  
  699.         if opt == "-t" or opt == "--trace":
  700.             trace = 1
  701.             continue
  702.  
  703.         if opt == "-c" or opt == "--count":
  704.             count = 1
  705.             continue
  706.  
  707.         if opt == "-r" or opt == "--report":
  708.             report = 1
  709.             continue
  710.  
  711.         if opt == "-R" or opt == "--no-report":
  712.             no_report = 1
  713.             continue
  714.  
  715.         if opt == "-f" or opt == "--file":
  716.             counts_file = val
  717.             continue
  718.  
  719.         if opt == "-m" or opt == "--missing":
  720.             missing = 1
  721.             continue
  722.  
  723.         if opt == "-C" or opt == "--coverdir":
  724.             coverdir = val
  725.             continue
  726.  
  727.         if opt == "-s" or opt == "--summary":
  728.             summary = 1
  729.             continue
  730.  
  731.         if opt == "--ignore-module":
  732.             ignore_modules.append(val)
  733.             continue
  734.  
  735.         if opt == "--ignore-dir":
  736.             for s in val.split(os.pathsep):
  737.                 s = os.path.expandvars(s)
  738.                 # should I also call expanduser? (after all, could use $HOME)
  739.  
  740.                 s = s.replace("$prefix",
  741.                               os.path.join(sys.prefix, "lib",
  742.                                            "python" + sys.version[:3]))
  743.                 s = s.replace("$exec_prefix",
  744.                               os.path.join(sys.exec_prefix, "lib",
  745.                                            "python" + sys.version[:3]))
  746.                 s = os.path.normpath(s)
  747.                 ignore_dirs.append(s)
  748.             continue
  749.  
  750.         assert 0, "Should never get here"
  751.  
  752.     if listfuncs and (count or trace):
  753.         _err_exit("cannot specify both --listfuncs and (--trace or --count)")
  754.  
  755.     if not (count or trace or report or listfuncs or countcallers):
  756.         _err_exit("must specify one of --trace, --count, --report, "
  757.                   "--listfuncs, or --trackcalls")
  758.  
  759.     if report and no_report:
  760.         _err_exit("cannot specify both --report and --no-report")
  761.  
  762.     if report and not counts_file:
  763.         _err_exit("--report requires a --file")
  764.  
  765.     if no_report and len(prog_argv) == 0:
  766.         _err_exit("missing name of file to run")
  767.  
  768.     # everything is ready
  769.     if report:
  770.         results = CoverageResults(infile=counts_file, outfile=counts_file)
  771.         results.write_results(missing, summary=summary, coverdir=coverdir)
  772.     else:
  773.         sys.argv = prog_argv
  774.         progname = prog_argv[0]
  775.         sys.path[0] = os.path.split(progname)[0]
  776.  
  777.         t = Trace(count, trace, countfuncs=listfuncs,
  778.                   countcallers=countcallers, ignoremods=ignore_modules,
  779.                   ignoredirs=ignore_dirs, infile=counts_file,
  780.                   outfile=counts_file)
  781.         try:
  782.             t.run('execfile(%r)' % (progname,))
  783.         except IOError, err:
  784.             _err_exit("Cannot run file %r because: %s" % (sys.argv[0], err))
  785.         except SystemExit:
  786.             pass
  787.  
  788.         results = t.results()
  789.  
  790.         if not no_report:
  791.             results.write_results(missing, summary=summary, coverdir=coverdir)
  792.  
  793. if __name__=='__main__':
  794.     main()
  795.